﻿namespace PlaylistCopy.Helpers
{
    using CuttingEdge.Conditions;
    using PlaylistCopy.Contract;
    using PlaylistCopy.Extensions;
    using PlaylistCopy.Localization;
    using PlaylistCopy.Logging;
    using PlaylistCopy.Services;
    using System;
    using System.Collections.Generic;
    using System.IO;
    using System.Linq;

    internal class PlaylistCopyEngine
    {
        private readonly PlaylistEngineOptImpl options;

        internal PlaylistCopyEngine(Action<IPlaylistCopyEngineOptions> options)
        {
            Condition.Requires(options, "options").IsNotNull();

            this.options = new PlaylistEngineOptImpl();
            options(this.options);
        }

        private Localizer T
        {
            get { return this.options.T; }
        }

        private ILogger Logger
        {
            get { return this.options.Logger; }
        }

        internal void Copy()
        {
            this.options.Logger.Info(this.T("CheckingConfigurtion"));

            this.CheckOptions();

            using (var playlistFile = new FileStream(this.options.PlaylistFile, FileMode.Open))
            {
                this.Logger.Info(this.T("PlaylistFileOpened"));

                var filesToProcess = this.options.Playlist.GetMusicFiles(playlistFile).ToList();
                if (!filesToProcess.Any())
                {
                    return;
                }

                this.Logger.Info(this.T("GotMusicFileList", filesToProcess.Count));

                var root = this.ResolveRootDirectory(filesToProcess);
                this.Logger.Info(this.T("RootDirectoryResolved", root));

                var currentFile = 0;

                foreach (var musicFile in filesToProcess.TakeWhile(musicFile => !this.ShouldCancel()))
                {
                    this.Logger.Info(this.T("ProcessingFile", musicFile));

                    string targetFileName;
                    if (string.IsNullOrWhiteSpace(root) || !musicFile.StartsWith(root))
                    {
                        targetFileName = Path.GetFileName(musicFile);
                    }
                    else
                    {
                        targetFileName = musicFile.Remove(0, root.Length).Trim('\\');
                    }

                    this.Logger.Info(this.T("RelativeMusicPath", targetFileName));
                    if (targetFileName == null)
                    {
                        this.options.Logger.Error(this.T("RelativeMusicPathIsNull"));
                        return;
                    }

                    var targetPath = Path.Combine(this.options.OutputDirectory, targetFileName);
                    this.Logger.Info(this.T("TargetPathBuilt", targetFileName));

                    var targetDirectory = Path.GetDirectoryName(targetPath);

                    if (!this.options.DirectoryService.DirectoryExist(targetDirectory))
                    {
                        this.Logger.Warning(this.T("DirectoryDoesntExistSoCreating", targetDirectory));
                        this.options.DirectoryService.CreateDirectory(targetDirectory);
                    }

                    if (this.ShouldCancel())
                    {
                        break;
                    }

                    this.Logger.Info(this.T("Copying"));
                    this.options.FileCopyService.Copy(musicFile, targetPath, this.options.OverwriteFiles);

                    this.options.WorkArguments.ReportProgress(currentFile/filesToProcess.Count, this.T("Copying"),
                                                              targetFileName);
                    currentFile++;
                }
            }

            this.Logger.Info(this.T("CopyingComplete"));
        }

        private bool ShouldCancel()
        {
            if (!this.options.WorkArguments.IsCancelling)
            {
                return false;
            }

            this.Logger.Warning(this.T("CancellingCopying"));
            return true;
        }

        private string ResolveRootDirectory(IList<string> musicFiles)
        {
            Condition.Requires(musicFiles, "musicFiles").IsNotNull();

            switch (this.options.CopyMode)
            {
                case CopyMode.TryPreserveStructure:
                    var commonPath = musicFiles.GetCommonStartingSubString();
                    if (commonPath.Length == 0)
                    {
                        this.Logger.Warning("CannotResolveCommonRootFolder");
                        return null;
                    }

                    if (!commonPath.EndsWith("\\"))
                    {
                        var lastPathSeparator = commonPath.LastIndexOf("\\", StringComparison.InvariantCulture);
                        commonPath = commonPath.Substring(0, lastPathSeparator);
                    }


                    return string.IsNullOrWhiteSpace(commonPath) ? null : commonPath.Trim('\\');

                case CopyMode.Direct:
                    return null;

                case CopyMode.PreserveStrcture:
                    return this.options.RootDirectory;

                default:
                    throw new NotSupportedException("CopyMode " + this.options.CopyMode + " is not supported.");
            }
        }

        private void CheckOptions()
        {
            if (this.options.PlaylistFile == null)
            {
                throw new InvalidOperationException("Input playlist is not specified.");
            }

            if (this.options.Playlist == null)
            {
                throw new InvalidOperationException("Playlist provider is not specified.");
            }

            if (this.options.OutputDirectory == null)
            {
                throw new InvalidOperationException("Output directory is not specified.");
            }

            if (this.options.WorkArguments == null)
            {
                throw new InvalidOperationException("Work arguments are not specified.");
            }

            if (this.options.FileCopyService == null)
            {
                throw new InvalidOperationException("File move service is null.");
            }

            if (this.options.DirectoryService == null)
            {
                throw new InvalidOperationException("DIrectory service is null.");
            }

            if (this.options.CopyMode != CopyMode.PreserveStrcture)
            {
                return;
            }

            if (this.options.RootDirectory == null)
            {
                throw new InvalidOperationException("Root folder is not specifed. Cannot preserve structure.");
            }
        }


        private class PlaylistEngineOptImpl : IPlaylistCopyEngineOptions
        {
            internal PlaylistEngineOptImpl()
            {
                this.CopyMode = CopyMode.Direct;
                this.Logger = NullLogger.Instance;
                this.T = NullLocalizer.Instance;
            }

            internal IPlaylist Playlist
            {
                get; private set;
            }

            internal IWorkArguments WorkArguments { get; private set; }

            internal string PlaylistFile { get; private set; }

            internal string OutputDirectory { get; private set; }

            internal CopyMode CopyMode { get; private set; }

            internal string RootDirectory { get; private set; }

            internal IFileService FileCopyService { get; private set; }

            internal IDirectoryService DirectoryService { get; private set; }

            internal ILogger Logger { get; private set; }

            internal Localizer T { get; private set; }

            internal bool OverwriteFiles { get; private set; }

            public IPlaylistCopyEngineOptions WithPlaylist(IPlaylist playlist)
            {
                Condition.Requires(playlist, "playlist").IsNotNull();
                this.Playlist = playlist;

                return this;
            }

            public IPlaylistCopyEngineOptions WithWorkArguments(IWorkArguments arguments)
            {
                Condition.Requires(arguments, "arguments").IsNotNull();

                this.WorkArguments = arguments;
                return this;
            }

            public IPlaylistCopyEngineOptions WithPlaylistFile(string file)
            {
                Condition.Requires(file, "file").IsNotNull();

                this.PlaylistFile = file;
                return this;
            }

            public IPlaylistCopyEngineOptions WithOutputDirectory(string output)
            {
                Condition.Requires(output, "output").IsNotNull();

                this.OutputDirectory = output;
                return this;
            }

            public IPlaylistCopyEngineOptions DirectCopy()
            {
                this.CopyMode = CopyMode.Direct;
                this.RootDirectory = null;

                return this;
            }

            public IPlaylistCopyEngineOptions TryPreserveStructure()
            {
                this.CopyMode = CopyMode.TryPreserveStructure;
                this.RootDirectory = null;

                return this;
            }

            public IPlaylistCopyEngineOptions PreserveStructure(string root)
            {
                Condition.Requires(root, "root").IsNotNull();

                this.CopyMode = CopyMode.PreserveStrcture;
                this.RootDirectory = root;

                return this;
            }

            public IPlaylistCopyEngineOptions WithCopyService(IFileService copyService)
            {
                Condition.Requires(copyService, "copyService").IsNotNull();

                this.FileCopyService = copyService;
                return this;
            }

            public IPlaylistCopyEngineOptions WithDirectoryService(IDirectoryService directoryService)
            {
                Condition.Ensures(directoryService, "directoryService").IsNotNull();

                this.DirectoryService = directoryService;
                return this;
            }

            public IPlaylistCopyEngineOptions WithLogger(ILogger logger)
            {
                this.Logger = logger ?? NullLogger.Instance;
                return this;
            }

            public IPlaylistCopyEngineOptions WithLocalizer(Localizer localizer)
            {
                this.T = localizer ?? NullLocalizer.Instance;
                return this;
            }

            public IPlaylistCopyEngineOptions AllowOverwritingFiles(bool allow)
            {
                this.OverwriteFiles = allow;
                return this;
            }
        }
    }

    internal interface IPlaylistCopyEngineOptions
    {
        IPlaylistCopyEngineOptions WithPlaylist(IPlaylist playlist);
        IPlaylistCopyEngineOptions WithWorkArguments(IWorkArguments arguments);
        IPlaylistCopyEngineOptions WithPlaylistFile(string file);
        IPlaylistCopyEngineOptions WithOutputDirectory(string output);
        IPlaylistCopyEngineOptions DirectCopy();
        IPlaylistCopyEngineOptions TryPreserveStructure();
        IPlaylistCopyEngineOptions PreserveStructure(string root);
        IPlaylistCopyEngineOptions WithCopyService(IFileService copyService);
        IPlaylistCopyEngineOptions WithDirectoryService(IDirectoryService directoryService);
        IPlaylistCopyEngineOptions WithLogger(ILogger logger);
        IPlaylistCopyEngineOptions WithLocalizer(Localizer localizer);
        IPlaylistCopyEngineOptions AllowOverwritingFiles(bool allow);
    }

    public enum CopyMode
    {
        Direct,
        TryPreserveStructure,
        PreserveStrcture
    }
}
